/*
 *
 * HDF is dual licensed: you can use it either under the terms of
 * the GPL, or the BSD license, at your option.
 * See the LICENSE file in the root of this repository for complete details.
 */

#include "panel_simple_common.h"
#include "gpio_if.h"
#include "hdf_bl.h"
#include "hdf_disp.h"
#include "osal.h"

static inline struct panel_simple *to_panel_simple(const struct drm_panel *panel)
{
    return container_of(panel, struct panel_simple, panel);
}

static inline struct panel_simple *to_panel_simple_by_data(const struct PanelData *panel_data)
{
    return container_of(panel_data, struct panel_simple, panel_data);
}

static int panel_simple_parse_cmd_seq(struct device *dev,
				      const u8 *data, int length,
				      struct panel_cmd_seq *seq)
{
	struct panel_cmd_header *header;
	struct panel_cmd_desc *desc;
	char *buf, *d;
	unsigned int i, cnt, len;

	if (!seq)
		return -EINVAL;

	buf = (u8 *)OsalMemCalloc(length);
	if (!buf)
		return -ENOMEM;
	memcpy_s(buf, length, data, length);

	d = buf;
	len = length;
	cnt = 0;
	while (len > sizeof(*header)) {
		header = (struct panel_cmd_header *)d;

		d += sizeof(*header);
		len -= sizeof(*header);

		if (header->payload_length > len)
			return -EINVAL;

		d += header->payload_length;
		len -= header->payload_length;
		cnt++;
	}

	if (len)
		return -EINVAL;

	seq->cmd_cnt = cnt;
	seq->cmds = (struct panel_cmd_desc *)OsalMemCalloc(sizeof(*desc));
	if (!seq->cmds)
		return -ENOMEM;

	d = buf;
	len = length;
	for (i = 0; i < cnt; i++) {
		header = (struct panel_cmd_header *)d;
		len -= sizeof(*header);
		d += sizeof(*header);

		desc = &seq->cmds[i];
		desc->header = *header;
		desc->payload = d;

		d += header->payload_length;
		len -= header->payload_length;
	}

	return 0;
}

static int32_t panel_simple_regulator_enable(struct panel_simple *p)
{
    int32_t err;
    
	if (p->power_invert) {
		if (regulator_is_enabled(p->supply) > 0)
			regulator_disable(p->supply);
	} else {
		err = regulator_enable(p->supply);
		if (err < 0)
			return err;
	}

    return 0;
}

static int32_t panel_simple_regulator_disable(struct panel_simple *p)
{
    int err;

	if (p->power_invert) {
		if (!regulator_is_enabled(p->supply)) {
			err = regulator_enable(p->supply);
			if (err < 0)
				return err;
		}
	} else {
		regulator_disable(p->supply);
	}

	return 0;
}

int panel_simple_loader_protect(struct drm_panel *panel)
{
    struct panel_simple *p = to_panel_simple(panel);
	int err;

	err = panel_simple_regulator_enable(p);
	if (err < 0) {
		HDF_LOGE("failed to enable supply: %d\n", err);
		return err;
	}

	p->prepared = true;
	p->enabled = true;

	return 0;
}
EXPORT_SYMBOL(panel_simple_loader_protect);

static int panel_simple_get_hpd_gpio(struct device *dev,
				     struct panel_simple *p, bool from_probe)
{
	int err;

	p->hpd_gpio = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN);
	if (IS_ERR(p->hpd_gpio)) {
		err = PTR_ERR(p->hpd_gpio);

		/*
		 * If we're called from probe we won't consider '-EPROBE_DEFER'
		 * to be an error--we'll leave the error code in "hpd_gpio".
		 * When we try to use it we'll try again.  This allows for
		 * circular dependencies where the component providing the
		 * hpd gpio needs the panel to init before probing.
		 */
		if (err != -EPROBE_DEFER || !from_probe) {
			HDF_LOGE("failed to get 'hpd' GPIO: %d\n", err);
			return err;
		}
	}

	return 0;
}

#define PANEL_SIMPLE_BOUNDS_CHECK(to_check, bounds, field) \
	(to_check->field.typ >= bounds->field.min && \
	 to_check->field.typ <= bounds->field.max)
static void panel_simple_parse_panel_timing_node(struct device *dev,
						 struct panel_simple *panel,
						 const struct display_timing *ot)
{
	const struct panel_desc *desc = panel->desc;
	struct videomode vm;
	unsigned int i;

	if (WARN_ON(desc->num_modes)) {
		HDF_LOGE("Reject override mode: panel has a fixed mode\n");
		return;
	}
	if (WARN_ON(!desc->num_timings)) {
		HDF_LOGE("Reject override mode: no timings specified\n");
		return;
	}

	for (i = 0; i < panel->desc->num_timings; i++) {
		const struct display_timing *dt = &panel->desc->timings[i];

		if (!PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hactive) ||
		    !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hfront_porch) ||
		    !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hback_porch) ||
		    !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hsync_len) ||
		    !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vactive) ||
		    !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vfront_porch) ||
		    !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vback_porch) ||
		    !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vsync_len))
			continue;

		if (ot->flags != dt->flags)
			continue;

		videomode_from_timing(ot, &vm);
		drm_display_mode_from_videomode(&vm, &panel->override_mode);
		panel->override_mode.type |= DRM_MODE_TYPE_DRIVER |
					     DRM_MODE_TYPE_PREFERRED;
		break;
	}

	if (WARN_ON(!panel->override_mode.type))
		HDF_LOGE("Reject override mode: No display_timing found\n");
}

static int32_t PanelOn(struct PanelData *data)
{
    struct panel_simple *p = to_panel_simple_by_data(data);

	if (p->enabled)
		return HDF_SUCCESS;

	if (p->desc->delay.enable)
		OsalMSleep(p->desc->delay.enable);

	p->enabled = true;

    return HDF_SUCCESS;
}

static int32_t PanelOff(struct PanelData *data)
{
    struct panel_simple *simplePanel = NULL;

    simplePanel = to_panel_simple_by_data(data);
    if (simplePanel->desc->delay.disable) {
        OsalMSleep(simplePanel->desc->delay.disable);
    }
    return HDF_SUCCESS;
}

static int32_t PanelPrepare(struct PanelData *data)
{
    struct panel_simple *p = to_panel_simple_by_data(data);
	unsigned int delay;
	int err;
	int hpd_asserted;

	if (p->prepared)
		return HDF_SUCCESS;

	err = panel_simple_regulator_enable(p);
	if (err < 0) {
		HDF_LOGE("failed to enable supply: %d\n", err);
		return err;
	}

	gpiod_direction_output(p->enable_gpio, 1);

	if (p->desc->delay.reset)
		OsalMSleep(p->desc->delay.reset);

	gpiod_direction_output(p->reset_gpio, 1);

	if (p->desc->delay.reset)
		OsalMSleep(p->desc->delay.reset);

	gpiod_direction_output(p->reset_gpio, 0);

	delay = p->desc->delay.prepare;
	if (p->no_hpd)
		delay += p->desc->delay.hpd_absent_delay;
	if (delay)
		OsalMSleep(delay);

	// if (p->hpd_gpio) {
	// 	if (IS_ERR(p->hpd_gpio)) {
	// 		err = panel_simple_get_hpd_gpio(panel->dev, p, false);
	// 		if (err)
	// 			return err;
	// 	}

	// 	err = readx_poll_timeout(gpiod_get_value_cansleep, p->hpd_gpio,
	// 				 hpd_asserted, hpd_asserted,
	// 				 1000, 2000000);
	// 	if (hpd_asserted < 0)
	// 		err = hpd_asserted;

	// 	if (err) {
	// 		HDF_LOGE("error waiting for hpd GPIO: %d\n", err);
	// 		return err;
	// 	}
	// }

	// if (p->desc->init_seq)
	// 	if (p->dsi)
	// 		panel_simple_xfer_dsi_cmd_seq(p, p->desc->init_seq);

	if (p->desc->delay.init)
		OsalMSleep(p->desc->delay.init);

	p->prepared = true;

    return HDF_SUCCESS;
}

static int32_t PanelUnprepare(struct PanelData *data)
{
    int32_t ret;
    struct panel_simple *p = NULL;

    p = to_panel_simple_by_data(data);

	if (!p->prepared)
		return HDF_SUCCESS;

    gpiod_direction_output(p->reset_gpio, 1);
    gpiod_direction_output(p->enable_gpio, 0);

    panel_simple_regulator_disable(p);

    if (p->desc->delay.unprepare) {
        OsalMSleep(p->desc->delay.unprepare);
    }

    return HDF_SUCCESS;
}

static int32_t PanelInit(struct PanelData *panel)
{
    return 0;
}

static int32_t panel_simple_probe(struct device *dev, struct panel_simple *panel)
{
	const struct panel_desc *desc = panel->desc;
    struct display_timing dt;
    int connector_type;
	u32 bus_flags;
    int32_t err;

    panel->no_hpd = of_property_read_bool(dev->of_node, "no-hpd");
	if (!panel->no_hpd) {
		err = panel_simple_get_hpd_gpio(dev, panel, true);
		if (err){
			HDF_LOGE("%s Get hpd gpio fail %d", __func__, err);
		    goto FAIL;
        }
	}

    panel->supply = devm_regulator_get(dev, "power");
    if (IS_ERR(panel->supply)) {
        HDF_LOGE("%s Get regulator fail %d", __func__, PTR_ERR(panel->supply));
        goto FAIL;
    }

    panel->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_ASIS);
    if (IS_ERR(panel->enable_gpio)) {
        HDF_LOGE("%s get enable_gpio fail %d", __func__, PTR_ERR(panel->enable_gpio));
        goto FAIL;
    }

    panel->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS);
    if (IS_ERR(panel->reset_gpio)) {
        HDF_LOGE("get reset_gpio fail %d", __func__, PTR_ERR(panel->reset_gpio));
        goto FAIL;
    }

    if (!of_get_display_timing(dev->of_node, "panel-timing", &dt))
		panel_simple_parse_panel_timing_node(dev, panel, &dt);

    connector_type = desc->connector_type;
	/* Catch common mistakes for panels. */
	switch (connector_type) {
	case 0:
		HDF_LOGD("Specify missing connector_type\n");
		connector_type = DRM_MODE_CONNECTOR_DPI;
		break;
	case DRM_MODE_CONNECTOR_LVDS:
		WARN_ON(desc->bus_flags &
			~(DRM_BUS_FLAG_DE_LOW |
			  DRM_BUS_FLAG_DE_HIGH |
			  DRM_BUS_FLAG_DATA_MSB_TO_LSB |
			  DRM_BUS_FLAG_DATA_LSB_TO_MSB));
		WARN_ON(desc->bus_format != MEDIA_BUS_FMT_RGB666_1X7X3_SPWG &&
			desc->bus_format != MEDIA_BUS_FMT_RGB888_1X7X4_SPWG &&
			desc->bus_format != MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA);
		WARN_ON(desc->bus_format == MEDIA_BUS_FMT_RGB666_1X7X3_SPWG &&
			desc->bpc != 6);
		WARN_ON((desc->bus_format == MEDIA_BUS_FMT_RGB888_1X7X4_SPWG ||
			 desc->bus_format == MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA) &&
			desc->bpc != 8);
		break;
	case DRM_MODE_CONNECTOR_eDP:
		if (desc->bus_format == 0)
			HDF_LOGW("Specify missing bus_format\n");
		if (desc->bpc != 6 && desc->bpc != 8)
			HDF_LOGW("Expected bpc in {6,8} but got: %u\n", desc->bpc);
		break;
	case DRM_MODE_CONNECTOR_DSI:
		if (desc->bpc != 6 && desc->bpc != 8)
			HDF_LOGW("Expected bpc in {6,8} but got: %u\n", desc->bpc);
		break;
	case DRM_MODE_CONNECTOR_DPI:
		bus_flags = DRM_BUS_FLAG_DE_LOW |
			    DRM_BUS_FLAG_DE_HIGH |
			    DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE |
			    DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE |
			    DRM_BUS_FLAG_DATA_MSB_TO_LSB |
			    DRM_BUS_FLAG_DATA_LSB_TO_MSB |
			    DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE |
			    DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE;
		if (desc->bus_flags & ~bus_flags)
			HDF_LOGW("Unexpected bus_flags(%d)\n", desc->bus_flags & ~bus_flags);
		if (!(desc->bus_flags & bus_flags))
			HDF_LOGW("Specify missing bus_flags\n");
		if (desc->bus_format == 0)
			HDF_LOGW("Specify missing bus_format\n");
		if (desc->bpc != 6 && desc->bpc != 8)
			HDF_LOGW("Expected bpc in {6,8} but got: %u\n", desc->bpc);
		break;
	default:
		HDF_LOGW("Specify a valid connector_type: %d\n", desc->connector_type);
		connector_type = DRM_MODE_CONNECTOR_DPI;
		break;
	}

    HDF_LOGI("%s success", __func__);
    return HDF_SUCCESS;
FAIL:
    return HDF_FAILURE;
}

static bool of_child_node_is_present(const struct device_node *node,
				     const char *name)
{
	struct device_node *child;

	child = of_get_child_by_name(node, name);
	of_node_put(child);

	return !!child;
}

static int panel_simple_of_get_desc_data(struct device *dev,
					 struct panel_desc *desc)
{
	struct device_node *np = dev->of_node;
	u32 bus_flags;
	const void *data;
	int len;
	int err;

	if (of_child_node_is_present(np, "display-timings")) {
		struct drm_display_mode *mode;

        mode = (struct drm_display_mode *)OsalMemCalloc(sizeof(*mode));
		if (!mode)
			return -ENOMEM;

		err = of_get_drm_display_mode(np, mode, &bus_flags,
					     OF_USE_NATIVE_MODE);
		if (!err) {
			desc->modes = mode;
			desc->num_modes = 1;
			desc->bus_flags = bus_flags;
		}
	} else if (of_child_node_is_present(np, "panel-timing")) {
		struct display_timing *timing;
		struct videomode vm;

        timing = (struct display_timing *)OsalMemCalloc(sizeof(*timing));
		if (!timing)
			return -ENOMEM;

		if (!of_get_display_timing(np, "panel-timing", timing)) {
			desc->timings = timing;
			desc->num_timings = 1;

			bus_flags = 0;
			vm.flags = timing->flags;
			drm_bus_flags_from_videomode(&vm, &bus_flags);
			desc->bus_flags = bus_flags;
		}
	}

	if (desc->num_modes || desc->num_timings) {
		of_property_read_u32(np, "bpc", &desc->bpc);
		of_property_read_u32(np, "bus-format", &desc->bus_format);
		of_property_read_u32(np, "width-mm", &desc->size.width);
		of_property_read_u32(np, "height-mm", &desc->size.height);
	}

	of_property_read_u32(np, "prepare-delay-ms", &desc->delay.prepare);
	of_property_read_u32(np, "enable-delay-ms", &desc->delay.enable);
	of_property_read_u32(np, "disable-delay-ms", &desc->delay.disable);
	of_property_read_u32(np, "unprepare-delay-ms", &desc->delay.unprepare);
	of_property_read_u32(np, "reset-delay-ms", &desc->delay.reset);
	of_property_read_u32(np, "init-delay-ms", &desc->delay.init);

	data = of_get_property(np, "panel-init-sequence", &len);
	if (data) {
        desc->init_seq = (struct panel_cmd_seq *)OsalMemCalloc(sizeof(*desc->init_seq));
		if (!desc->init_seq)
			return -ENOMEM;

		err = panel_simple_parse_cmd_seq(dev, data, len,
						 desc->init_seq);
		if (err) {
			HDF_LOGE("failed to parse init sequence\n");
			return err;
		}
	}

	data = of_get_property(np, "panel-exit-sequence", &len);
	if (data) {
        desc->exit_seq = (struct panel_cmd_seq *)OsalMemCalloc(sizeof(*desc->exit_seq));
		if (!desc->exit_seq)
			return -ENOMEM;

		err = panel_simple_parse_cmd_seq(dev, data, len,
						 desc->exit_seq);
		if (err) {
			HDF_LOGE("failed to parse exit sequence\n");
			return err;
		}
	}

	return 0;
}

#define BLK_PWM_INDEX             2
#define PWM_MAX_PERIOD            40000
/* backlight setting */
#define MIN_LEVEL                 0
#define MAX_LEVEL                 255
#define DEFAULT_LEVEL             127

static struct PanelInfo g_panelInfo = {
    .width = 720,          /* width */
    .height = 1280,          /* height */
    .hbp = 40,             /* horizontal back porch */
    .hfp = 40,         /* horizontal front porch */
    .hsw = 10,              /* horizontal sync width */
    .vbp = 15,              /* vertical back porch */
    .vfp = 10,              /* vertical front porch */
    .vsw = 36,               /* vertical sync width */
    .clockFreq = 75000000,  /* clock */
    .pWidth = 68,           /* physical width */
    .pHeight = 121,         /* physical height */
    .connectorType = DRM_MODE_CONNECTOR_DPI,   /* DRM_MODE_CONNECTOR_DPI=17 */
    .blk = { BLK_PWM, MIN_LEVEL, MAX_LEVEL, DEFAULT_LEVEL },
};

static void PanelDataInit(struct panel_simple *panel, struct HdfDeviceObject *object)
{
	struct PanelData *panel_data = &panel->panel_data;
	panel_data->object = object;
    panel_data->init = PanelInit;
    panel_data->on = PanelOn;
    panel_data->off = PanelOff;
    panel_data->prepare = PanelPrepare;
    panel_data->unprepare = PanelUnprepare;
	panel_data->info = &g_panelInfo;
	panel_data->priv = panel;
}

static int32_t PanelEntryInit(struct HdfDeviceObject *object)
{
	struct device_node *panelNode = NULL;
    struct platform_device *pdev = NULL;
	struct panel_simple *simplePanel = NULL;
	struct panel_desc *desc = NULL;
    int err;
	bool hasPanel = false;

	HDF_LOGI("PanelEntryInit");

	while((panelNode = of_find_compatible_node(panelNode, NULL, "simple-panel")) != NULL){
		hasPanel = true;

		pdev = of_find_device_by_node(panelNode);
		if (pdev == NULL) {
			HDF_LOGE("%s of_find_device_by_node fail", __func__);
			goto FAIL;
		}

		desc = (struct panel_desc *)OsalMemCalloc(sizeof(*desc));
		if (desc == NULL) {
			HDF_LOGE("%s panel_desc malloc fail", __func__);
			goto FAIL;
		}

		err = panel_simple_of_get_desc_data(&pdev->dev, desc);
		if (err) {
			HDF_LOGE("%s failed to get desc data: %d\n", __func__, err);
			goto FAIL;
		}

		simplePanel = (struct panel_simple *)OsalMemCalloc(sizeof(struct panel_simple));
		if (simplePanel == NULL) {
			HDF_LOGE("%s simplePanel malloc fail", __func__);
			goto FAIL;
		}
		simplePanel->desc = desc;
		simplePanel->panel.dev = &pdev->dev;

		err = panel_simple_probe(&pdev->dev, simplePanel);
		if (err) {
			HDF_LOGE("%s failed to panel_simple_probe: %d\n", __func__, err);
			goto FAIL;
		}

		PanelDataInit(simplePanel, object);

		if (RegisterPanel(&simplePanel->panel_data) != HDF_SUCCESS) {
			HDF_LOGE("RegisterPanel fail");
			goto FAIL;
		}
		
		of_node_put(panelNode);
	}

    if (!hasPanel) {
        HDF_LOGE("%s panel simple not found!!!", __func__);
        goto FAIL;
    }
    
    HDF_LOGI("%s success", __func__);
    return HDF_SUCCESS;
FAIL:
	OsalMemFree(desc);
	OsalMemFree(simplePanel);
    of_node_put(panelNode);
    return HDF_FAILURE;
}

struct HdfDriverEntry g_commonPanelSimpleDevEntry = {
    .moduleVersion = 1,
    .moduleName = "PANEL_SIMPLE_COMMON",
    .Init = PanelEntryInit,
};

HDF_INIT(g_commonPanelSimpleDevEntry);
